import numpy as np
from .common import TwoPlayersAbstractGameState, AbstractGameAction


class HexMove(AbstractGameAction):

    def __init__(self, x_coordinate, y_coordinate, value):
        self.x_coordinate = x_coordinate
        self.y_coordinate = y_coordinate
        self.value = value

    def __repr__(self):
        return "x:{0} y:{1} v:{2}".format(self.x_coordinate, self.y_coordinate,
                                          self.value)


class HexGameState(TwoPlayersAbstractGameState):

    r = 1
    b = -1

    def __init__(self, state, next_to_move=1, action=None):
        # if state is a string from protocol
        if isinstance(state, str):
            lines = state.split(",")
            board_size = len(lines[0])
            state = np.zeros((board_size, board_size))
            for i, line in enumerate(lines):
                for j, char in enumerate(line):
                    if char == "R":
                        state[i][j] = self.r
                    elif char == "B":
                        state[i][j] = self.b

        if len(state.shape) != 2 or state.shape[0] != state.shape[1]:
            raise ValueError("Only 2D square boards allowed")
        self.board = state
        self.board_size = state.shape[0]
        self.winner = None
        self.next_to_move = next_to_move
        self.action = action  # last action to reach current state
        self.neighbours = [(-1, 0), (1, 0), (0, -1), (0, 1), (-1, 1), (1, -1)]
        # used to check game result
        self.visit_status = np.zeros((self.board_size, self.board_size))

    @property
    def game_result(self):

        # Red
        # for all top tiles, check if they connect to bottom
        for idx in range(self.board_size):
            if (not self.visit_status[0][idx] and self.board[0][idx] == self.r
                    and self.winner is None):
                self.dfs_color(0, idx, self.r)
        # Blue
        # for all left tiles, check if they connect to right
        for idx in range(self.board_size):
            if (not self.visit_status[idx][0] and self.board[idx][0] == self.b
                    and self.winner is None):
                self.dfs_color(idx, 0, self.b)

        return self.winner

    def dfs_color(self, x, y, color):

        # check if (x, y) inside the board
        if x < 0 or x >= self.board_size or y < 0 or y >= self.board_size:
            return

        if self.board[x][y] != color:
            return

        if self.visit_status[x][y]:
            return
        self.visit_status[x][y] = 1

        # win conditions
        if color == self.r and x == self.board_size - 1:
            self.winner = color
        elif color == self.b and y == self.board_size - 1:
            self.winner = color

        if self.winner is not None:
            return

        for dx, dy in self.neighbours:
            xn, yn = x + dx, y + dy
            self.dfs_color(xn, yn, color)

    def is_game_over(self):
        return self.game_result is not None

    def is_move_legal(self, move):
        # check if correct player moves
        if move.value != self.next_to_move:
            return False

        # check if inside the board on x-axis
        x_in_range = (0 <= move.x_coordinate < self.board_size)
        if not x_in_range:
            return False

        # check if inside the board on y-axis
        y_in_range = (0 <= move.y_coordinate < self.board_size)
        if not y_in_range:
            return False

        # finally check if board field not occupied ye
        return self.board[move.x_coordinate, move.y_coordinate] == 0

    def move(self, move):
        if not self.is_move_legal(move):
            raise ValueError("move {0} on board {1} is not legal".format(
                move, self.board))
        new_board = np.copy(self.board)
        new_board[move.x_coordinate, move.y_coordinate] = move.value
        if self.next_to_move == self.r:
            next_to_move = self.b
        else:
            next_to_move = self.r
        return HexGameState(new_board, next_to_move,
                            (move.x_coordinate, move.y_coordinate))

    def get_legal_actions(self):
        indices = np.where(self.board == 0)
        return [
            HexMove(coords[0], coords[1], self.next_to_move)
            for coords in list(zip(indices[0], indices[1]))
        ]
